package jmine.tec.persist.web.pages.audit;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import jmine.tec.report.api.Report;
import jmine.tec.report.base.persist.auditoria.TrilhaAuditoriaReportCreator;
import jmine.tec.report.base.persist.auditoria.TrilhaAuditoriaReportParameter;
import jmine.tec.web.pages.HelpTextMessages;
import jmine.tec.web.wicket.component.MapsWicketDateTextField;
import jmine.tec.web.wicket.component.report.ReportTablePanel;
import jmine.tec.web.wicket.pages.Template;

import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.EmptyPanel;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.util.ListModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.hibernate.EntityMode;
import org.hibernate.metadata.ClassMetadata;

import bancosys.tec.exception.MessageCreator;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.audit.Auditable;
import bancosys.tec.persist.authorization.annotation.DisplayName;
import bancosys.tec.persist.hibernate.PartitionSessionFactory;
import bancosys.tec.persist.persister.PersistenceAction;
import bancosys.tec.security.annotation.Secure;
import bancosys.tec.security.authorization.UrlPermission;
import bancosys.tec.utils.date.Date;

/**
 * Tela de geração de consulta/relatório de trilhas de auditoria.
 * 
 * @author Renan Kuroki
 * @created Jan 12, 2011
 */
@Secure(id = "URL_REPORT_TRILHA_AUDITORIA", permissionType = UrlPermission.class)
public class ConsultaTrilhaAuditoria extends Template {

    public static final String DEFAULT_DATE_FORMAT = "dd/MM/yyyy";

    @SpringBean
    private TrilhaAuditoriaReportCreator reportCreator;

    @SpringBean
    private PartitionSessionFactory sessionFactory;

    private static Set<Class<? extends Persistable>> persistables;

    private Form<Object> form = null;

    private Date dataInicio = new Date();

    private Date dataFim = new Date();

    private String usuario;

    private ClasseAuditavelDropDown classeAuditavel;

    private PersistenceAction acao;

    private Collection<String> blackList = new LinkedList<String>();

    /**
     * Construtor.
     */
    public ConsultaTrilhaAuditoria() {
        super();

        // TODO definição da blacklist não ficará aqui..
        this.blackList.add("AdministradorFundo");

        this.initForm();
    }

    /**
     * Inicializa o form.
     */
    private void initForm() {
        this.form = new Form<Object>("mainForm") {
            @Override
            protected void onSubmit() {
                ConsultaTrilhaAuditoria.this.submitForm();
            }
        };
        this.add(new FeedbackPanel("feedback"));

        this.form.add(this.createClasseAuditavelSelect());
        this.form.add(new TextField<String>("usuario", new PropertyModel<String>(this, "usuario")));
        this.form.add(this.getDateFild("dataInicio", "dataInicio", true));
        this.form.add(this.getDateFild("dataFim", "dataFim", true));
        this.form.add(new EmptyPanel("report").setOutputMarkupId(true));
        this.form.add(this.createAcaoSelect());

        this.form.add(new Button("btnPesquisar"));

        this.add(this.form);
    }

    /**
     * Rodado no submit do form.
     */
    private void submitForm() {
        TrilhaAuditoriaReportParameter reportParameter = this.reportCreator.createParameter();
        reportParameter.setDataInicio(this.dataInicio == null ? null : new Date(this.dataInicio));
        reportParameter.setDataFim(this.dataFim == null ? null : new bancosys.tec.utils.date.Date(this.dataFim));
        reportParameter.setUsuario(this.usuario);
        reportParameter.setClasseAuditavel(this.getClasseAuditavel() == null ? null : this.getClasseAuditavel().getBean());

        List<Report> reports = this.reportCreator.createReports(reportParameter);
        Report report = reports.isEmpty() ? null : reports.get(0);
        ReportTablePanel panel = new ReportTablePanel("report", report);
        panel.setOutputMarkupId(true);
        panel.setVisible(true);

        // Removendo botão de geração de relatório XLS.
        WebMarkupContainer reportTable = (WebMarkupContainer) panel.get("reportTable");
        reportTable.get("exportXLS").setVisible(false);

        this.form.addOrReplace(panel);
    }

    /**
     * Cria o input select de {@link ClasseAuditavelDropDown}.
     * 
     * @return DropDownChoice
     */
    private DropDownChoice<ClasseAuditavelDropDown> createClasseAuditavelSelect() {
        List<ClasseAuditavelDropDown> items = new LinkedList<ClasseAuditavelDropDown>();
        for (Class<? extends Auditable> bean : this.getAuditableClasses()) {
            items.add(new ClasseAuditavelDropDown(bean));
        }
        Collections.sort(items, new java.util.Comparator<ClasseAuditavelDropDown>() {
            public int compare(ClasseAuditavelDropDown o1, ClasseAuditavelDropDown o2) {
                return o1.toString().compareTo(o2.toString());
            }
        });
        ListModel<ClasseAuditavelDropDown> listModel = new ListModel<ClasseAuditavelDropDown>(items);
        DropDownChoice<ClasseAuditavelDropDown> selecaoClassesAuditaveis =
                new DropDownChoice<ClasseAuditavelDropDown>("classeAuditavel", listModel);
        selecaoClassesAuditaveis.setRequired(false);
        selecaoClassesAuditaveis.setNullValid(true);
        selecaoClassesAuditaveis.setDefaultModel(new PropertyModel<String>(this, "classeAuditavel"));
        return selecaoClassesAuditaveis;
    }

    /**
     * Cria o input select de {@link PersistenceAction}.
     * 
     * @return DropDownChoice de Ações disponíveis
     */
    private DropDownChoice<PersistenceAction> createAcaoSelect() {
        List<PersistenceAction> items = new ArrayList<PersistenceAction>();
        items.add(PersistenceAction.insert);
        items.add(PersistenceAction.update);
        items.add(PersistenceAction.remove);
        ListModel<PersistenceAction> listModel = new ListModel<PersistenceAction>(items);
        DropDownChoice<PersistenceAction> selecaoAcoes = new DropDownChoice<PersistenceAction>("acao", listModel);
        selecaoAcoes.setRequired(false);
        selecaoAcoes.setNullValid(true);
        selecaoAcoes.setDefaultModel(new PropertyModel<String>(this, "acao"));
        return selecaoAcoes;
    }

    /**
     * Devolve uma lista das classes que são autorizáveis e possuem <code>DisplayName</code> definido.
     * 
     * @return uma lista das classes que são autorizáveis e possuem <code>DisplayName</code> definido.
     */
    public List<Class<? extends Auditable>> getAuditableClasses() {
        List<Class<? extends Auditable>> auditables = new LinkedList<Class<? extends Auditable>>();
        for (Class<? extends Persistable> persistable : this.getPersistables()) {
            if (Auditable.class.isAssignableFrom(persistable)) {
                Class<? extends Auditable> auditable = persistable.asSubclass(Auditable.class);

                DisplayName displayName = auditable.getAnnotation(DisplayName.class);
                if ((displayName != null) && displayName.showAuth()) {
                    if (!this.ignore(auditable.getSimpleName())) {
                        auditables.add(auditable);
                    }
                }
            }
        }
        return auditables;
    }

    /**
     * Método que verifica se a classe deve ser mostrada no combo de classes da pesquisa, a partir de uma blacklist.
     * 
     * @param value nome da classe a ser verificada.
     * @return boolean true se deve ser ignorada.
     */
    private boolean ignore(String value) {
        for (String ignoreRegex : this.blackList) {
            if (value.matches(ignoreRegex)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Recupera as classes persistíveis.
     * 
     * @return lista com as classes persistíveis.
     */
    private synchronized Set<Class<? extends Persistable>> getPersistables() {
        if (persistables == null) {
            persistables = new HashSet<Class<? extends Persistable>>();
            Collection<?> mappedClasses = this.sessionFactory.getAllClassMetadata().values();
            for (Object obj : mappedClasses) {
                ClassMetadata classMetadata = (ClassMetadata) obj;
                Class<?> mappedClass = classMetadata.getMappedClass(EntityMode.POJO);
                if (Persistable.class.isAssignableFrom(mappedClass)) {
                    persistables.add(mappedClass.asSubclass(Persistable.class));
                }
            }
        }
        return persistables;
    }

    /**
     * @return the dataInicio
     */
    public Date getDataInicio() {
        return this.dataInicio;
    }

    /**
     * @param dataInicio the dataInicio to set
     */
    public void setDataInicio(Date dataInicio) {
        this.dataInicio = dataInicio;
    }

    /**
     * @return the dataFim
     */
    public Date getDataFim() {
        return this.dataFim;
    }

    /**
     * @param dataFim the dataFim to set
     */
    public void setDataFim(Date dataFim) {
        this.dataFim = dataFim;
    }

    /**
     * @param usuario the usuario to set
     */
    public void setUsuario(String usuario) {
        this.usuario = usuario;
    }

    /**
     * @return the usuario
     */
    public String getUsuario() {
        return this.usuario;
    }

    /**
     * @param classeAuditavel the classeAuditavel to set
     */
    public void setClasseAuditavel(ClasseAuditavelDropDown classeAuditavel) {
        this.classeAuditavel = classeAuditavel;
    }

    /**
     * @return the classeAuditavel
     */
    public ClasseAuditavelDropDown getClasseAuditavel() {
        return this.classeAuditavel;
    }

    /**
     * @return the acao
     */
    public PersistenceAction getAcao() {
        return this.acao;
    }

    /**
     * @param acao the acao to set
     */
    public void setAcao(PersistenceAction acao) {
        this.acao = acao;
    }

    /**
     * Retorna um {@link MapsWicketDateTextField}.
     * 
     * @param wicketId WicketId
     * @param key Atributo
     * @param required Required
     * @return MapsWicketDateTextField
     */
    private MapsWicketDateTextField getDateFild(String wicketId, String key, boolean required) {
        return MapsWicketDateTextField.forDatePattern(wicketId, new PropertyModel<Date>(this, key), required, true);
    }

    /**
     * Inner Class auxiliar da criação do dropdownchoice de classes 'auditáveis'.
     * 
     * @author Renan Kuroki
     * @created Jan 12, 2011
     */
    protected class ClasseAuditavelDropDown implements Serializable {

        private Class<? extends Auditable> bean;

        /**
         * Construtor
         * 
         * @param bean bean referente a lista
         */
        public ClasseAuditavelDropDown(Class<? extends Auditable> bean) {
            this.bean = bean;
        }

        /**
         * @return the bean
         */
        public Class<? extends Auditable> getBean() {
            return this.bean;
        }

        /**
         * @param bean the bean to set
         */
        public void setBean(Class<? extends Auditable> bean) {
            this.bean = bean;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            DisplayName displayName = this.bean.getAnnotation(DisplayName.class);
            return displayName.value();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected MessageCreator getHelpTextCreator() {
        return HelpTextMessages.CONSULTA_TRILHA_AUDITORIA;
    }
}
